package com.chs.recoginition.controller;

import com.chs.recoginition.core.exception.ApplicationException;
import com.chs.recoginition.core.exception.constant.ExceptionConstant;
import com.chs.recoginition.vo.Result;
import com.chs.recoginition.core.utils.ValidatorUtils;
import com.chs.recoginition.vo.auth.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;

import static com.chs.recoginition.core.exception.constant.ExceptionConstant.*;

@Slf4j
@RestController
@ControllerAdvice
public class GlobalErrorController implements ErrorController {

    @Resource
    private ErrorAttributes errorAttributes;

    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping("/error")
    public ResponseEntity<Result> handleError(HttpServletRequest request, Throwable throwable) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (throwable == null) {
            throwable = errorAttributes.getError(new ServletWebRequest(request));
        }

        if (throwable instanceof ApplicationException) {
            return getResponseEntityByApplicationException((ApplicationException) throwable);
        } else if (throwable instanceof AccessDeniedException) {
            String username = null;
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null) {
                username = ((UserVo) authentication.getPrincipal()).getUsername();
            }
            log.info("user:{}, url:{}", username, request.getRequestURL());
            statusCode = 403;
        } else if (throwable instanceof BadCredentialsException) {
            return new ResponseEntity<>(Result.error(USERNAME_OR_PASSWORD_ERR), HttpStatus.OK);
        } else if (throwable instanceof HttpMessageNotReadableException) {
            String message = throwable.getMessage();
            if (message.contains("Required request body is missing")) {
                return handleError(request, new ApplicationException(PARAM_NOT_NULL));
            }
            if (message.contains("JSON parse error")) {
                if (message.contains("[") && message.contains("]") && message.contains(";")) {
                    String errorCode = filterSpecialCharacter(message.substring(message.indexOf("[") + 1, message.indexOf("]")));
                    String errorMessage = filterSpecialCharacter(message.substring(message.indexOf("]") + 1, message.lastIndexOf(";")));
                    return handleError(request, new ApplicationException(errorCode, errorMessage));
                } else {
                    return handleError(request, new ApplicationException(JSON_FORMAT_FAIL));
                }
            }
        } else if (throwable instanceof InternalAuthenticationServiceException) {
            String message = throwable.getMessage();
            if (message.contains("[") && message.contains("]")) {
                String errorCode = filterSpecialCharacter(message.substring(message.indexOf("[") + 1, message.indexOf("]")));
                String errorMessage = filterSpecialCharacter(message.substring(message.indexOf("]") + 1));
                return handleError(request, new ApplicationException(errorCode, errorMessage));
            }
        } else if (throwable.getMessage() != null
                && throwable.getMessage().contains("ApplicationException: ")
                && throwable.getMessage().contains("[")
                && throwable.getMessage().contains("]")) {
            String message = throwable.getMessage();
            message = message.substring(message.lastIndexOf("ApplicationException"));
            String errorCode = filterSpecialCharacter(message.substring(message.indexOf("[") + 1, message.indexOf("]")));
            String errorMessage = filterSpecialCharacter(message.substring(message.indexOf("]") + 1));
            return handleError(request, new ApplicationException(errorCode, errorMessage));
        }

        if (statusCode == null) {
            return new ResponseEntity<>(Result.error(ERROR), HttpStatus.OK);
        } else if (statusCode == 401) {
            return new ResponseEntity<>(Result.error(UNAUTHORIZED), HttpStatus.valueOf(statusCode));
        } else if (statusCode == 404) {
            return new ResponseEntity<>(Result.error(API_NOT_EXISTS), HttpStatus.valueOf(statusCode));
        } else if (statusCode == 403) {
            return new ResponseEntity<>(Result.error(BEYOND_THE_PERMISSIONS), HttpStatus.valueOf(statusCode));
        } else {
            return new ResponseEntity<>(Result.error(ERROR), HttpStatus.valueOf(statusCode));
        }
    }

    private ResponseEntity<Result> getResponseEntityByApplicationException(ApplicationException oaException) {
        if (UNAUTHORIZED.getErrorCode().equals(oaException.getErrorCode())) {
            return new ResponseEntity<>(Result.error(oaException), HttpStatus.UNAUTHORIZED);
        } else if (API_FORBIDDEN.getErrorCode().equals(oaException.getErrorCode())
                || BEYOND_THE_PERMISSIONS.getErrorCode().equals(oaException.getErrorCode())) {
            return new ResponseEntity<>(Result.error(oaException), HttpStatus.FORBIDDEN);
        } else if (API_NOT_EXISTS.getErrorCode().equals(oaException.getErrorCode())) {
            return new ResponseEntity<>(Result.error(oaException), HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<>(Result.error(oaException), HttpStatus.OK);
    }

    /**
     * 全局异常捕捉处理
     */
    @ExceptionHandler(value = Throwable.class)
    public ResponseEntity<Result> errorHandler(HttpServletRequest request, Throwable throwable) {
        if (isLog(throwable)) {
            log.error(throwable.getMessage(), throwable);
        }
        return handleError(request, throwable);
    }

    private boolean isLog(Throwable throwable) {
        boolean isInstanceOf = throwable instanceof BadCredentialsException // 用户名密码错误
                || throwable instanceof AccessDeniedException // 没有权限
                ;
        return !isInstanceOf;
    }

    /**
     * Validator 参数校验异常处理
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity<Object> handleMethodVoArgumentNotValidException(MethodArgumentNotValidException ex) {
        String msg = ValidatorUtils.getErrorMessage(ex.getBindingResult());
        return new ResponseEntity<>(Result.error(ExceptionConstant.ILLEGAL_PARAM.getErrorCode(), msg), HttpStatus.OK);
    }

    /**
     * Validator 参数校验异常处理
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResponseEntity<Result> handleMethodArgumentNotValidException(ConstraintViolationException ex) {
        String msg = ValidatorUtils.getErrorMessage(ex.getConstraintViolations());
        return new ResponseEntity<>(Result.error(ExceptionConstant.ILLEGAL_PARAM.getErrorCode(), msg), HttpStatus.OK);
    }

    /**
     * Validator 参数校验异常处理
     */
    @ExceptionHandler(value = BindException.class)
    public ResponseEntity<Result> handleBindException(BindException ex) {
        String msg = ValidatorUtils.getErrorMessage(ex);
        return new ResponseEntity<>(Result.error(ExceptionConstant.ILLEGAL_PARAM.getErrorCode(), msg), HttpStatus.OK);
    }

    private String filterSpecialCharacter(String str) {
        if (str == null) {
            return null;
        }
        return str.trim().replaceAll("(]|/|\\*|\\$|\\^|\\(|\\)|\\{|\\[|\\||\\?|\\.|\\+)*", "");
    }
}